use super::role::RoleVo;
use super::Result;
use crate::entity::{prelude::SysResource, sys_role_resource};
use crate::entity::{sys_resource, sys_role};
use crate::util;
use sea_orm::{
    ActiveModelTrait, ColumnTrait, ConnectionTrait, DbConn, DerivePartialModel, EntityTrait,
    FromQueryResult, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, Set,
};
use serde::{Deserialize, Serialize};
use validator::Validate;

pub struct Resource;

/// 资源的详细数据
#[derive(Debug, DerivePartialModel, FromQueryResult, Serialize)]
#[sea_orm(entity = "SysResource")]
pub struct ResourceVo {
    pub id: i32,
    pub path: String,
    pub method: String,
    pub description: String,
    pub status: bool,
    pub created_at: chrono::NaiveDateTime,
    pub updated_at: chrono::NaiveDateTime,
}

impl From<sys_resource::Model> for ResourceVo {
    fn from(value: sys_resource::Model) -> Self {
        Self {
            id: value.id,
            path: value.path,
            method: value.method,
            description: value.description,
            status: value.status,
            created_at: value.created_at,
            updated_at: value.updated_at,
        }
    }
}

/// 资源提交的数据
#[derive(Debug, Deserialize, Validate)]
pub struct ResourceDo {
    #[validate(length(min = 1, max = 255), non_control_character)]
    pub path: String,
    #[validate(length(min = 1, max = 32))]
    pub method: String,
    #[validate(length(max = 255))]
    #[serde(default)]
    pub description: String,
    pub status: bool,
}

impl Into<sys_resource::ActiveModel> for ResourceDo {
    /// 将提交数据转换成[sys_resource::ActiveModel]
    /// - 同时设置updated_at
    /// - 没有设置created_at
    fn into(self) -> sys_resource::ActiveModel {
        sys_resource::ActiveModel {
            path: Set(self.path),
            method: Set(self.method),
            description: Set(self.description),
            status: Set(self.status),
            updated_at: Set(util::current_datetime()),
            ..Default::default()
        }
    }
}

impl Resource {
    /// 获取全部的资源
    pub async fn all(db: &DbConn) -> Result<Vec<ResourceVo>> {
        SysResource::find()
            .order_by_asc(sys_resource::Column::Path)
            .into_partial_model()
            .all(db)
            .await
    }

    /// 分页列出资源
    pub async fn list(db: &DbConn, page: u64, page_size: u64) -> Result<(u64, Vec<ResourceVo>)> {
        let sql = SysResource::find()
            .order_by_asc(sys_resource::Column::Path)
            .into_partial_model()
            .paginate(db, page_size);
        let total = sql.num_items().await?;
        let users = sql.fetch_page(page).await?;
        Ok((total, users))
    }

    pub async fn fetch_one(db: &DbConn, id: i32) -> Result<Option<ResourceVo>> {
        sys_resource::Entity::find_by_id(id)
            .into_partial_model()
            .one(db)
            .await
    }

    pub async fn create<C>(db: &C, data: ResourceDo) -> Result<ResourceVo>
    where
        C: ConnectionTrait,
    {
        let mut res: sys_resource::ActiveModel = data.into();
        res.created_at = Set(util::current_datetime());
        res.insert(db).await.map(Into::into)
    }

    /// 根据id更新资源, 当id不存在时，抛出错误
    pub async fn update<C>(db: &C, id: i32, data: ResourceDo) -> Result<ResourceVo>
    where
        C: ConnectionTrait,
    {
        let mut res: sys_resource::ActiveModel = data.into();
        res.id = Set(id);
        res.update(db).await.map(Into::into)
    }

    /// 根据id删除资源, 当id不存在时，抛出错误
    pub async fn delete<C>(db: &C, id: i32) -> Result<sea_orm::DeleteResult>
    where
        C: ConnectionTrait,
    {
        sys_role_resource::Entity::delete_many()
            .filter(sys_role_resource::Column::ResourceId.eq(id))
            .exec(db)
            .await?;
        sys_resource::Entity::delete_by_id(id).exec(db).await
    }

    pub async fn fetch_roles(db: &DbConn, id: i32) -> Result<Vec<RoleVo>> {
        sys_role::Entity::find()
            .join_rev(
                sea_orm::JoinType::LeftJoin,
                sys_role_resource::Entity::belongs_to(sys_role::Entity)
                    .from(sys_role_resource::Column::RoleId)
                    .to(sys_role::Column::Id)
                    .into(),
            )
            .filter(sys_role_resource::Column::ResourceId.eq(id))
            .into_partial_model()
            .all(db)
            .await
    }
}
